﻿using System;
using System.Collections.Generic;
using System.Xml;
using BEPUphysics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Nine;
using Nine.Graphics;
using Nine.Graphics.Effects;
using Nine.Graphics.Primitives;
using LevelComponents;
using LevelComponents.Character;
using EventFlowLibrary.Player;
using BEPUphysics.DataStructures;
using BEPUphysics.MathExtensions;
using BEPUphysics.CollisionShapes.ConvexShapes;
using BEPUphysics.Collidables;
using Microsoft.Xna.Framework.Input;
using PerformanceUtility.ShapeRendering;
using BEPUphysics.Entities.Prefabs;
using PerformanceUtility.GameDebugTools;
using System.Xml.Linq;
using System.Linq;
using System.IO;
using System.Text;
using BEPUphysics.Settings;
using Microsoft.Xna.Framework.Audio;
using EventFlowLibrary.GameStructure;
using Nine.Animations;
using System.Globalization;

namespace EventFlowLibrary.LevelComponents
{
    /// <summary>
    /// </summary>
    internal sealed class LevelManagement : DrawableGameComponent
    {
        /// <summary>
        /// World reference containing models, characters, and physics
        /// </summary>
        private World world;

        /// <summary>
        /// The BEPU physics space for the game level. X is up in this game world.
        /// </summary> 
        internal Space GameSpace { get { return gameSpace; } }
        private Space gameSpace;

        /// <summary>
        /// First person camera for the game.
        /// </summary>
        public Camera FPSCamera { get { return fpsCamera; } }
        private Camera fpsCamera;

        /// <summary>
        /// FPS Character for the game.
        /// </summary>
        public CharacterControllerInput Character { get { return character; } }
        private CharacterControllerInput character;

        internal List<HSGNode> hsgNodes;

        /// <summary>
        /// List of all non-moving models in the game
        /// </summary>
        internal Dictionary<string, StaticModel> StaticModels { get { return staticModels; } }
        private Dictionary<string, StaticModel> staticModels;

        /// <summary>
        /// List of all moving models in the game
        /// </summary>
        internal Dictionary<string, DynamicModel> DynamicModels { get { return dynamicModels; } }
        private Dictionary<string, DynamicModel> dynamicModels;

        /// <summary>
        /// Our animation player for manager models.
        /// </summary>
        internal AnimationPlayer Animations { get { return animations; } }
        private AnimationPlayer animations;

        /// <summary>
        /// List of all animated models in the game.
        /// </summary>
        internal Dictionary<string, AnimatedModel> AnimatedModels { get { return animatedModels; } }
        private Dictionary<string, AnimatedModel> animatedModels;

        /// <summary>
        /// Direction of the light, from position and target
        /// </summary>
        private Vector3 lightDir = Vector3.Normalize(new Vector3(-0.1f, 1, -0.7f));

        /// <summary>
        /// Light viewprojection for shadows.
        /// </summary>
        //private Matrix lightViewProjection;
        
        /// <summary>
        /// XNA component for texture drawing.
        /// </summary>
        SpriteBatch spriteBatch;

        /// <summary>
        /// Engine Nine component responsible for drawing scene 
        /// </summary>
        ModelBatch modelBatch;

        /// <summary>
        /// Batch for helping draw debug shapes.
        /// </summary>
        PrimitiveBatch primitiveBatch;

        /// <summary>
        /// Engine Nine shadow map
        /// </summary>
        ShadowMap shadowMap;
        
        /// <summary>
        /// Skymap for outside levels.
        /// </summary>
        TextureCube skyMap;

        /// <summary>
        /// A place holder value for the shadow frustum. To increase speed, replace references of this with Camera.Frustum.
        /// This, however, adds for better shadows, as they don't jump in/out depending on camera view.
        /// </summary>
        //BoundingFrustum shadowFrustum;

        /// <summary>
        /// A display journal to allow the user to track their progress.
        /// </summary>
        internal Journal journal;

        /// <summary>
        /// Number of frames between checking whether everyone is innocent.
        /// Hack since I didn't allow for "AND" game state comparisons.
        /// </summary>
        private int numFramesBetweenInnocentChecked = 0;
        private const int numFramesBetweenInnocentMaxChecks = 50;

        /// <summary>
        /// Garbage free constants
        /// </summary>
        private const string journalText = "Journal";
        private const string journalUpdatedText = "Journal Updated";

        /// <summary>
        /// Journal updated variables
        /// </summary>
        private float fadeSpeed;
        private float alphaValue = 0.0f;

        /// <summary>
        /// Whether to show the journal updating signage
        /// </summary>
        internal bool ShowJournalUpdating { get { return showJournalUpdating; } set { showJournalUpdating = value; } }
        private bool showJournalUpdating = false;
        private int timeShown = 0;
        private const float showDuration = 6000f;

        SpriteFont font;
        Texture2D yButton;
        Rectangle journalButtonLocation;
        Vector2 journalTextLocation;
        Vector2 journalUpdatedTextLocation;
        private bool drawSkyBox = true;

        /// <summary>
        /// Number of frames we've drawn in this level. Used to prevent double drawing using HSG.
        /// </summary>
        int frameCount = -1;

        /// <summary>
        /// Debug information.
        /// </summary>
        int modelsAttemptedToDraw, modelsDrawn, verticesAttemptedToDraw, verticesDrawn;

        /// <summary>
        /// Construct a level manager.
        /// TODO construct documentation: check box test file names/configs.
        /// </summary>
        /// <param name="_game"></param>
        /// <param name="_XmlFile"></param>
        /// <param name="_world"></param>
        internal LevelManagement(Game _game, LoadVariables _loadVariables, World _world)
            : base(_game)
        {
//#if TRACE
//            //DebugSystem.Instance.EventDisplayer.EnqueueDisplayEvent("New level loaded: " + _loadVariables.level);
//            DebugSystem.Instance.GameDebugger.SetWorld(_world);
//#endif

            // Track container
            world = _world;

            // Initialize the physics space
            gameSpace = new Space();
            gameSpace.ForceUpdater.Gravity = new Vector3(0, -79.81f, 0); //9.81f is supposed to be for the earth

            // Init animation player
            animations = new AnimationPlayer();

            // Set up the FPS Camera
            fpsCamera = new Camera(_loadVariables.position, 0.5f, _loadVariables.cameraPitch, _loadVariables.cameraYaw, Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, (float)this.Game.GraphicsDevice.PresentationParameters.BackBufferWidth / this.Game.GraphicsDevice.PresentationParameters.BackBufferHeight, .1f, 1000), this.world);

            // Set up the character, active, and hide the body. Attach the camera.
            character = new CharacterControllerInput(gameSpace, fpsCamera, _loadVariables.position, 10.0f, 1f, 100);
            character.CharacterController.Body.Tag = "noDisplayObject";
            fpsCamera.SetCameraMode(CameraMode.FirstPersonMovement, character.CharacterController.Body, new Vector3(0, 2.0f, 0), true, 15);

            // Init lists
            hsgNodes = new List<HSGNode>();
            staticModels = new Dictionary<string, StaticModel>();
            dynamicModels = new Dictionary<string, DynamicModel>();
            animatedModels = new Dictionary<string, AnimatedModel>();

            // Add and create the journal
            journal = new Journal(this.Game, this.world.screenManager);
            this.Game.Components.Add(journal);
        }

        public override void Initialize()
        {
            base.Initialize();

            // Set up draw items
            spriteBatch = this.world.screenManager.SpriteBatch;

            // Start with first animation
            foreach (KeyValuePair<string, AnimatedModel> k in animatedModels)
            {
                //animations.Play(new BoneAnimation(k.Value.modelSkeleton, k.Value.model.GetAnimation(0)));
                animations[k.Value.ID].Play(new BoneAnimation(k.Value.modelSkeleton, k.Value.DisplayModel.GetAnimation(0)));
            }

            // All specific to a 1280 x 720 screen
            journalButtonLocation = new Rectangle(128, 720 - 72 - 32, 32, 32);
            journalTextLocation = new Vector2(128 + 42, 720 - 72 - 32);
            //journalUpdatedTextLocation = new Vector2(640 - (font.MeasureString(journalUpdatedText).X / 2), 72);
            journalUpdatedTextLocation = new Vector2(128, 360 - (font.LineSpacing / 2));

            if (GlobalVariables.loadVariables.level == "Content/XML/InsideLevel.xml")
            {
                drawSkyBox = false;
            }
        }

        protected override void LoadContent()
        {
            // Prep Nine for drawing
            modelBatch = new ModelBatch(GraphicsDevice);
            primitiveBatch = new PrimitiveBatch(GraphicsDevice);

            // Create shadow map related effects, depth is used to generate shadow maps, shadow is used to draw a shadow receiver with a shadow map.
            shadowMap = new ShadowMap(GraphicsDevice, 2048);

            // Enable shadowmap bluring, this will produce smoothed shadow edge. You can increase the shadow quality by using a larger shadowmap resolution (like 1024 or 2048), and increase the blur samples.
            shadowMap.BlurEnabled = true;
            
            // Create the content manager
            ContentManager content = new ContentManager(this.Game.Content.ServiceProvider, "Content");

            // Load the sky cube
            skyMap = content.Load<TextureCube>("Textures/skybox");

            // Load journal access items
            font = content.Load<SpriteFont>("Fonts/gamefontSmall");
            yButton = content.Load<Texture2D>("Textures/Buttons/xboxControllerButtonY");

            // Load our level via xml
            XElement xel = XElement.Load(GlobalVariables.loadVariables.level);
            XmlReader reader;
            reader = xel.CreateReader();

            // Read the info
            while (reader.Read())
            {
                // Check we've found an element. Use a "switch" statement to access if we need more.
                if (reader.NodeType == XmlNodeType.Element)
                {
                    switch (reader.Name)
                    {
                        case "HSGBoxes":
                            while (reader.Read())
                            {
                                // Add the state
                                if (reader.Name == "HSGBox" && reader.NodeType == XmlNodeType.Element)
                                {
                                    string[] min = reader["Min"].ToString().Split(',');
                                    string[] max = reader["Max"].ToString().Split(',');

                                    hsgNodes.Add(new HSGNode(reader["ID"].ToString(),
                                        new BoundingBox(new Vector3((float)Convert.ToDecimal(min[0], CultureInfo.InvariantCulture), (float)Convert.ToDecimal(min[1], CultureInfo.InvariantCulture), (float)Convert.ToDecimal(min[2], CultureInfo.InvariantCulture)),
                                                        new Vector3((float)Convert.ToDecimal(max[0], CultureInfo.InvariantCulture), (float)Convert.ToDecimal(max[1], CultureInfo.InvariantCulture), (float)Convert.ToDecimal(max[2], CultureInfo.InvariantCulture)))));
                                }

                                // Skip out of Boxes when dead
                                if (reader.Name == "HSGBoxes" && reader.NodeType == XmlNodeType.EndElement)
                                {
                                    break;
                                }
                            }
                            break;
                        case "StillModels":
                            // Loop and load the models
                            while (reader.Read())
                            {
                                // Start loading our new text frame
                                if (reader.Name == "Model" && reader.NodeType == XmlNodeType.Element)
                                {
                                    // Parse the matrix variables
                                    string[] pos = reader["Position"].ToString().Split(',');
                                    string[] rot = reader["Rotation"].ToString().Split(',');
                                    string[] scale = reader["Scale"].ToString().Split(',');

                                    Vector3 s = new Vector3((float)Convert.ToDouble(scale[0], CultureInfo.InvariantCulture), (float)Convert.ToDouble(scale[1], CultureInfo.InvariantCulture), (float)Convert.ToDouble(scale[2], CultureInfo.InvariantCulture));
                                    Vector3 r = new Vector3((float)Convert.ToDouble(rot[0], CultureInfo.InvariantCulture), (float)Convert.ToDouble(rot[1], CultureInfo.InvariantCulture), (float)Convert.ToDouble(rot[2], CultureInfo.InvariantCulture));
                                    Vector3 p = new Vector3((float)Convert.ToDouble(pos[0], CultureInfo.InvariantCulture), (float)Convert.ToDouble(pos[1], CultureInfo.InvariantCulture), (float)Convert.ToDouble(pos[2], CultureInfo.InvariantCulture));

                                    // Collission types: 0 = none, 1 = static, 2 = dynamic
                                    int collisionType = Convert.ToInt32(reader["CollisionType"].ToString(), CultureInfo.InvariantCulture);
                                    Vector3[] staticTriangleVertices = new Vector3[0];
                                    int[] staticTriangleIndices = new int[0];

                                    // Only generate mesh if we want to from the editor
                                    if (collisionType == 1 || collisionType == 2)
                                    {
                                        // Get vertices
                                        TriangleMesh.GetVerticesAndIndicesFromModel(content.Load<Model>(reader["FileName"].ToString()), out staticTriangleVertices, out staticTriangleIndices);
                                    }

                                    // If animated model, bypass the rest. Ugly fix for now.
                                    string[] animations = reader["Animations"].ToString().Split(',');
                                    if (animations[0].Length > 0)
                                    {
                                        animatedModels.Add(reader["ID"].ToString(), new AnimatedModel(reader["ID"].ToString(), content.Load<Model>(reader["FileName"].ToString()), content.Load<LinkedEffect>("Effects/SkinnedEffect"), content.Load<Model>("Models/Objects/cylinder_1height_point5radius"), this.gameSpace, s, r, p));
                                    }
                                    else
                                    {
                                        // Create type
                                        if (collisionType == 0 ||
                                            collisionType == 1)
                                        {
                                            // Create our static mesh to track it
                                            StaticMesh staticMesh = null;
                                            if (collisionType == 1)
                                            {
                                                staticMesh = new StaticMesh(staticTriangleVertices, staticTriangleIndices, new AffineTransform(s,
                                                    Quaternion.CreateFromRotationMatrix(Matrix.CreateRotationX(r.X) * Matrix.CreateRotationY(r.Y) * Matrix.CreateRotationZ(r.Z)), p));
                                                staticMesh.Sidedness = TriangleSidedness.Counterclockwise;

                                                // Add to the space
                                                gameSpace.Add(staticMesh);
                                            }

                                            // Add the model to our scene to draw if we are drawing it (i.e. not a bounding model, which are added exclusively below this if statement)
                                            if (reader["HSGNodes"].Length > 0)
                                            {
                                                // Create the model
                                                staticModels.Add(reader["ID"].ToString(), new StaticModel(reader["ID"].ToString(), 
                                                    content.Load<Model>(reader["FileName"].ToString()),
                                                    s, r, p, 
                                                    gameSpace, staticMesh,
                                                    Convert.ToBoolean(reader["DisplayShadow"].ToString(), CultureInfo.InvariantCulture), true));

                                                // Set the effect for drawing with Nine
                                                LevelSupport.LoadShadowEffect(staticModels[reader["ID"].ToString()].DisplayModel, reader["Effect"].ToString(), content, Matrix.Identity);// lightViewProjection);

                                                // Assign it to relevant nodes
                                                string[] nodes = reader["HSGNodes"].Split(',');
                                                for (int i = 0; i < nodes.Length; i++)
                                                {
                                                    for (int j = 0; j < hsgNodes.Count; j++)
                                                    {
                                                        if (nodes[i] == hsgNodes[j].ID)
                                                        {
                                                            hsgNodes[j].ModelsAttached.Add(staticModels[reader["ID"].ToString()]);
                                                            break;
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                        else if (collisionType == 2)
                                        {
                                            // Scale accordingly
                                            for (int i = 0; i < staticTriangleVertices.Length; i++)
                                            {
                                                staticTriangleVertices[i].X *= s.X;
                                                staticTriangleVertices[i].Y *= s.Y;
                                                staticTriangleVertices[i].Z *= s.Z;
                                            }

                                            // Create the shape
                                            var convexHull = new ConvexHull(p, staticTriangleVertices, (float)Convert.ToDouble(reader["Mass"].ToString(), CultureInfo.InvariantCulture));

                                            // Find the mass' center
                                            Vector3 center;
                                            var convexHullShape = new ConvexHullShape(staticTriangleVertices, out center);

                                            // Add the model to our scene to draw if we are drawing it (i.e. not a bounding model, which are added exclusively below this if statement)
                                            //if (Convert.ToBoolean(reader["DisplayModel"].ToString()))
                                            //{
                                                dynamicModels.Add(reader["ID"].ToString(), new DynamicModel(reader["ID"].ToString(), 
                                                    content.Load<Model>(reader["FileName"].ToString()),
                                                    convexHull, (float)Convert.ToDouble(reader["Mass"].ToString(), CultureInfo.InvariantCulture),
                                                    p, s, r, Vector3.Zero - center,
                                                    Convert.ToBoolean(reader["DisplayShadow"].ToString(), CultureInfo.InvariantCulture)));

                                                // Set the effect for drawing with Nine
                                                LevelSupport.LoadShadowEffect(dynamicModels[reader["ID"].ToString()].DisplayModel, reader["Effect"].ToString(), content, Matrix.Identity);// lightViewProjection);
                                            //}

                                            // Add to the space
                                            gameSpace.Add(convexHull);
                                        }
                                    }
                                }


                                // Skip out of this set of models when we've reached the end
                                if (reader.Name == "StillModels" && reader.NodeType == XmlNodeType.EndElement)
                                {
                                    break;
                                }   // Exit StillModels case
                            }
                            break;  // Exit StillModels case
                    }
                }
            }

            reader.Close();            

            base.LoadContent();

            // Moved models according to save data
            // Temp: ugly way to do it, with different model lists
            if (GlobalVariables.loadVariables.modelPositioning.ContainsKey(GlobalVariables.loadVariables.level))
            {
                foreach(KeyValuePair<string, ModelVariables> model in GlobalVariables.loadVariables.modelPositioning[GlobalVariables.loadVariables.level])
                {
                    if (staticModels.ContainsKey(model.Key))
                    {
                        staticModels[model.Key].SetPosition(model.Value.position);
                        staticModels[model.Key].SetRotation(model.Value.rotation);
                        staticModels[model.Key].SetScale(model.Value.scale);
                    }
                    else if (dynamicModels.ContainsKey(model.Key))
                    {
                        dynamicModels[model.Key].SetPosition(model.Value.position);
                        dynamicModels[model.Key].SetRotation(model.Value.rotation);
                        dynamicModels[model.Key].SetScale(model.Value.scale);
                    }
                    else if (animatedModels.ContainsKey(model.Key))
                    {
                        animatedModels[model.Key].SetPosition(model.Value.position);
                        animatedModels[model.Key].SetRotation(model.Value.rotation);
                        animatedModels[model.Key].SetScale(model.Value.scale);
                    }
                }
            }
        }

        public override void Draw(GameTime gameTime)
        {
            frameCount++;

            modelsAttemptedToDraw = modelsDrawn = verticesAttemptedToDraw = verticesDrawn = 0;

            // Move the light with the character - cheap cascading
            //lightViewProjection = Matrix.CreateLookAt(character.CharacterController.Body.Position + new Vector3(60, 200, 20), character.CharacterController.Body.Position, Vector3.UnitY) *
            //                      Matrix.CreateOrthographic(400, 400, 1, 300);
            //shadowFrustum = new BoundingFrustum(lightViewProjection);

            // Draw models with Nine
            GraphicsDevice.BlendState = BlendState.AlphaBlend;
            GraphicsDevice.DepthStencilState = DepthStencilState.Default;

            //if (staticModels.Count > 0 || dynamicModels.Count > 0)
            //{
                //shadowMap.Begin();
                //{
                //    // Clear everything to white. This is required.
                //    GraphicsDevice.BlendState = BlendState.Opaque;

                //    // Draw shadow casters using depth effect with the matrices set to light view and projection.
                //    modelBatch.Begin(Matrix.Identity, lightViewProjection);

                //    // Draw static models
                //    for (int i = 0; i < staticModels.Count; i++)
                //    {
                //        // Basic frustum culling, with shadow draw check
                //        if (!staticModels[i].DisplayShadow || !staticModels[i].Box.Intersects(shadowFrustum))
                //            continue;

                //        // TODO changes between 9/10 depending on if normal
                //        // Set the lightview - poor man's cascading
                //        for (int j = 0; j < staticModels[i].StaticModel.Meshes.Count; j++)
                //        {
                //            for (int k = 0; k < staticModels[i].StaticModel.Meshes[j].MeshParts.Count; k++)
                //            {
                //                for (int l = 0; l < staticModels[i].StaticModel.Meshes[j].MeshParts[k].Effect.Parameters.Count; l++)
                //                {
                //                    string s = staticModels[i].StaticModel.Meshes[j].MeshParts[k].Effect.Parameters[l].Name;
                //                }
                //                //staticModels[i].StaticModel.Meshes[j].MeshParts[k].Effect.Parameters["SpotLight8_SpotLightPosition"].SetValue(fpsCamera.Position);
                                
                //                // TODO this changes for each linked effect
                //                //DebugSystem.Instance.EventDisplayer.EnqueueDisplayEvent((fpsCamera.Position + fpsCamera.WorldMatrix.Forward * 20).ToString());
                //                //staticModels[i].StaticModel.Meshes[j].MeshParts[k].Effect.Parameters["PointLight10_PointLightPosition"].SetValue(fpsCamera.Position);// + fpsCamera.WorldMatrix.Forward * 20);
                //                //staticModels[i].StaticModel.Meshes[j].MeshParts[k].Effect.Parameters["ShadowMap12_LightViewProjection"].SetValue(lightViewProjection);
                //            }
                //        }

                //        // Draw the model
                //        modelBatch.Draw(staticModels[i].StaticModel, staticModels[i].WorldMatrix, shadowMap.Effect);
                //    }

                //    // Draw dynamic models
                //    for (int i = 0; i < dynamicModels.Count; i++)
                //    {
                //        if (!dynamicModels[i].DisplayShadow || !dynamicModels[i].PhysicsModel.CollisionInformation.BoundingBox.Intersects(shadowFrustum))
                //            continue;

                //        // TODO changes between 9/10 depending on if normal
                //        // Set the lightview - poor man's cascading
                //        for (int j = 0; j < dynamicModels[i].DynamicModel.Meshes.Count; j++)
                //        {
                //            for (int k = 0; k < dynamicModels[i].DynamicModel.Meshes[j].MeshParts.Count; k++)
                //            {
                //                // TODO this changes for each linked effect
                //                //dynamicModels[i].DynamicModel.Meshes[j].MeshParts[k].Effect.Parameters["PointLight10_PointLightPosition"].SetValue(fpsCamera.Position);// + fpsCamera.WorldMatrix.Forward * 20);
                //                //dynamicModels[i].DynamicModel.Meshes[j].MeshParts[k].Effect.Parameters["ShadowMap12_LightViewProjection"].SetValue(lightViewProjection);
                //            }
                //        }

                //        // Set the matrix
                //        dynamicModels[i].WorldMatrix = Matrix.CreateScale(dynamicModels[i].Scale) * Matrix.CreateTranslation(dynamicModels[i].MassOffset) * dynamicModels[i].PhysicsModel.WorldTransform;
                //        modelBatch.Draw(dynamicModels[i].DynamicModel, dynamicModels[i].WorldMatrix, shadowMap.Effect);
                //    }

                //    modelBatch.End();
                //}
                //// We got a shadow map rendered.
                //shadowMap.End();

                //// Set textures
                //for (int i = 0; i < staticModels.Count; i++)
                //{
                //    foreach (IEffectTexture effect in staticModels[i].StaticModel.GetEffects())
                //            effect.SetTexture(TextureUsage.ShadowMap, shadowMap.Texture);
                //}
                //for (int i = 0; i < dynamicModels.Count; i++)
                //{
                //    foreach (IEffectTexture effect in dynamicModels[i].DynamicModel.GetEffects())
                //        effect.SetTexture(TextureUsage.ShadowMap, shadowMap.Texture);
                //}
                //for (int i = 0; i < animatedModels.Count; i++)
                //{
                //    foreach (IEffectTexture effect in animatedModels[i].model.GetEffects())
                //        effect.SetTexture(TextureUsage.ShadowMap, shadowMap.Texture);
                //}

                // Now we begin drawing real scene.
                GraphicsDevice.Clear(Color.DarkSlateGray);

                // Draw all shadow receivers with the shadow effect
                modelBatch.Begin(ModelSortMode.Effect, fpsCamera.ViewMatrix, fpsCamera.ProjectionMatrix, null, null, null, RasterizerState.CullNone);
                {
                    if (drawSkyBox)
                    {
                        modelBatch.DrawSkyBox(skyMap);
                    }

                    for (int i = 0; i < hsgNodes.Count; i++)
                    {
                        if (character.CharacterController.Body.CollisionInformation.BoundingBox.Intersects(hsgNodes[i].Box))
                        {
                            //DebugSystem.Instance.EventDisplayer.EnqueueDisplayEvent("Intersecting " + hsgNodes[i].ID + " at frame " + frameCount);

                            for (int j = 0; j < hsgNodes[i].ModelsAttached.Count; j++)
                            {
                                modelsAttemptedToDraw++;
                                
                                // Don't draw multiple times if model in multiple accessed nodes this frame
                                if (hsgNodes[i].ModelsAttached[j].LastFrameDrawn == frameCount)
                                    continue;

                                modelsDrawn++;

                                // Draw the model
                                modelBatch.Draw(hsgNodes[i].ModelsAttached[j].DisplayModel, hsgNodes[i].ModelsAttached[j].WorldMatrix, null);

                                // Track we already drew this frame
                                hsgNodes[i].ModelsAttached[j].LastFrameDrawn = frameCount;
                            }
                        }
                    }

                    // Draw static models
                    //foreach (KeyValuePair<string, StaticModel> k in staticModels)
                    //{
                    //    // If we already drew this item this frame, don't do it again
                    //    if (k.Value.LastFrameDrawn == frameCount)
                    //        continue;

                    //    // Basic viewport checking
                    //    //if (!k.Value.TransformedBoundingBox.Intersects(fpsCamera.Frustum))
                    //    //    continue;

                    //    //DebugShapeRenderer.AddBoundingBox(k.Value.TransformedBoundingBox, Color.White);
                    //    modelBatch.Draw(k.Value.DisplayModel, k.Value.WorldMatrix, null);

                    //    k.Value.LastFrameDrawn++;
                    //}

                    // Draw dynamic models
                    foreach (KeyValuePair<string, DynamicModel> k in dynamicModels)
                    {
                        // If we already drew this item this frame, don't do it again
                        //if (k.Value.LastFrameDrawn == frameCount)
                        //    continue;

                        // Basic viewport checking
                        //if (!k.Value.PhysicsModel.CollisionInformation.BoundingBox.Intersects(fpsCamera.Frustum))
                        //    continue;

                        //DebugShapeRenderer.AddBoundingBox(k.Value.TransformedBoundingBox, Color.White);
                        // Set the matrix
                        k.Value.WorldMatrix = Matrix.CreateScale(k.Value.Scale) * Matrix.CreateTranslation(k.Value.MassOffset) * k.Value.PhysicsModel.WorldTransform;
                        modelBatch.Draw(k.Value.DisplayModel, k.Value.WorldMatrix, null);

                       // k.Value.LastFrameDrawn=framecount
                    }

                    // Draw animated models
                    foreach (KeyValuePair<string, AnimatedModel> k in animatedModels)
                    {
                        // If we already drew this item this frame, don't do it again
                        //if (k.Value.LastFrameDrawn == frameCount)
                        //    continue;

                        //if (!animatedModels[i].model

                        //DebugSystem.Instance.EventDisplayer.EnqueueDisplayEvent(string.Format("Character: {0}", character.CharacterController.Body.Position.ToString()));
                        //DebugShapeRenderer.AddBoundingBox(k.Value.TransformedBoundingBox, Color.White);
                        modelBatch.DrawSkinned(k.Value.DisplayModel, k.Value.WorldMatrix, k.Value.modelSkeleton.GetSkinTransforms(), null);

                        //k.Value.LastFrameDrawn=framecount
                    }
                }
                modelBatch.End();
            //}

            //DebugSystem.Instance.EventDisplayer.EnqueueDisplayEvent("Attempted: " + modelsAttemptedToDraw+ " Drawn: " + modelsDrawn);
            //DebugShapeRenderer.Draw(new GameTime(), FPSCamera.ViewMatrix, FPSCamera.ProjectionMatrix);

            // Show journal option when we can
            if (world.textManager.CurrentEvents.Count == 0 &&
                fpsCamera.Mode != CameraMode.Manual && 
                !world.textManager.letter.IsShown)
            {
                spriteBatch.Begin();

                // Bottom left corner with 10% buffer
                spriteBatch.Draw(yButton, journalButtonLocation, Color.White);
                spriteBatch.DrawString(font, journalText, journalTextLocation, Color.White);

                spriteBatch.End();
            }

            // Show journal updating sign when we can
            if (showJournalUpdating)
            {
                fadeSpeed = (float)gameTime.ElapsedGameTime.TotalSeconds * 3;
                alphaValue = Math.Min(alphaValue + fadeSpeed, 1);

                spriteBatch.Begin();

                spriteBatch.DrawString(font, journalUpdatedText, journalUpdatedTextLocation, Color.White * alphaValue);
                
                spriteBatch.End();

                // Check our duration
                timeShown += (int)gameTime.ElapsedGameTime.TotalMilliseconds;
                if (timeShown >= showDuration)
                {
                    showJournalUpdating = false;
                    timeShown = 0;
                }
            }
            else if (alphaValue > 0)
            {
                fadeSpeed = (float)gameTime.ElapsedGameTime.TotalSeconds * 3;
                alphaValue = Math.Max(alphaValue - fadeSpeed, 0);

                spriteBatch.Begin();

                spriteBatch.DrawString(font, journalUpdatedText, journalUpdatedTextLocation, Color.White * alphaValue);

                spriteBatch.End();
            }

            // Draw base objects
            base.Draw(gameTime);

        }

        public override void Update(GameTime gameTime)
        {
            // HACK: Just check if everyone's innocent and change accordingly
            // See variable declaration above...
            numFramesBetweenInnocentChecked++;
            if (numFramesBetweenInnocentChecked - numFramesBetweenInnocentMaxChecks == 0)
            {
                numFramesBetweenInnocentChecked = 0;

                if (GlobalVariables.loadVariables.gameState["GameState"] != "AllInnocent" &&
                    GlobalVariables.loadVariables.gameState["GameState"] != "BookNoticed" &&
                    GlobalVariables.loadVariables.gameState["GameState"] != "EndGame" &&
                    world.textManager.CurrentEvents.Count == 0)
                {
                    if ((GlobalVariables.loadVariables.gameState["AMarie"] == "EndGameTwo") &&
                        GlobalVariables.loadVariables.gameState["RChalcroft"] == "EndGame" &&
                        GlobalVariables.loadVariables.gameState["MJozef"] == "EndGame" &&
                        GlobalVariables.loadVariables.gameState["MajorAndrews"] == "EndGame" &&
                        GlobalVariables.loadVariables.gameState["LincolnGuilt"] == "Innocent" &&
                        GlobalVariables.loadVariables.gameState["RussellGuilt"] == "Innocent")
                    {
                        GlobalVariables.loadVariables.gameState["GameState"] = "AllInnocent";
                        world.eventManager.RefreshGameStates();
                        showJournalUpdating = true;
                    }
                }
            }

            // Update the physics world
            gameSpace.Update((float)gameTime.ElapsedGameTime.TotalSeconds);

            // Change our animation player according to time
            animations.Update(gameTime.ElapsedGameTime);

            base.Update(gameTime);
        }

        public void HandleInput(GameTime _gameTime, InputState _input)
        {
#if WINDOWS
            Mouse.SetPosition(200, 200);
#endif

            // Prevent us from moving the camera when in a text event
            // or when a letter is on the screen
            if (world.textManager.CurrentEvents.Count == 0 && 
                !world.textManager.letter.IsShown &&
                !journal.IsShown)
            {
                // Update the camera
#if !WINDOWS
                fpsCamera.Update((float)_gameTime.ElapsedGameTime.TotalSeconds, _input.CurrentGamePadStates[GlobalVariables.playerIndexInt]);
#else
                fpsCamera.Update((float)_gameTime.ElapsedGameTime.TotalSeconds, _input.CurrentMouseState);
#endif

                // Update character
                //character.Update(gameSpace.TimeStepSettings.TimeStepDuration, _input.LastKeyboardStates, _input.CurrentKeyboardStates, _input.LastGamePadStates, _input.CurrentGamePadStates);
                character.Update((float)_gameTime.ElapsedGameTime.TotalSeconds, _input.LastKeyboardStates[GlobalVariables.playerIndexInt], _input.CurrentKeyboardStates[GlobalVariables.playerIndexInt], _input.LastGamePadStates[GlobalVariables.playerIndexInt], _input.CurrentGamePadStates[GlobalVariables.playerIndexInt]);
            }
            else if (fpsCamera.Mode == CameraMode.Manual)
            {
                // this else allows us to overlay text with a manual camera.
#if !WINDOWS
                fpsCamera.Update((float)_gameTime.ElapsedGameTime.TotalSeconds, _input.CurrentGamePadStates[GlobalVariables.playerIndexInt]);
#else
                fpsCamera.Update((float)_gameTime.ElapsedGameTime.TotalSeconds, _input.CurrentMouseState);
#endif
            }
            else
            {
                // Stop movemement if in text
                character.CharacterController.HorizontalMotionConstraint.MovementDirection = Vector2.Zero;
            }

            // Show the journal
            if (world.textManager.CurrentEvents.Count == 0 && 
                fpsCamera.Mode != CameraMode.Manual &&
                !world.textManager.letter.IsShown)
            {
#if WINDOWS
                if (_input.IsNewKeyPress(Keys.Y, GlobalVariables.playerIndex))
#else
                    if (_input.IsNewButtonPress(Buttons.Y, GlobalVariables.playerIndex))
#endif
                {
                    journal.IsShown = !journal.IsShown;
                    AudioManager.soundEffects["pageflip3"].PlaySound(1.0f, 0.0f, 0.0f, false);
                }

//#if TRACE
//#if WINDOWS
//                if (_input.IsNewKeyPress(Keys.F1, GlobalVariables.playerIndex) && journal.IsShown)
//#else
//                if (_input.IsNewButtonPress(Buttons.RightShoulder, GlobalVariables.playerIndex) && journal.IsShown)
//#endif
//                {
//                    journal.ShowDebug = !journal.ShowDebug;
//                }
//#endif
            }
        }
    }
}
